from __future__ import with_statement

import sys
import os
import shutil
import unittest
import traceback
import urllib2
import time
import server
from kineta import *


def errstr():
    return str(sys.exc_info()[1])


class ErrorTests(unittest.TestCase):

    def setUp(self):
        self._dbdir = "db"
        if os.path.isdir(self._dbdir):
            shutil.rmtree(self._dbdir)
        self.db = FileDB(self._dbdir)
        self.db.set_bg_error_cb(self.bgerror)

    def bgerror(self, exc_info):
        traceback.print_exception(*exc_info)

    def testInvalidFields(self):
        try:
            self.db.select(table = "x", fields = None).close()
            self.fail()
        except KinetaError:
            self.assertEqual(errstr(), "Fields cannot be None/null")

    def testInvalidInsert(self):
        try:
            self.db.insert_bulk("x", ("a", "b"), [(1, 2, 3)])
            self.fail()
        except KinetaError:
            self.assertEqual(errstr(),
                            "Length of columns and values do not match (2, 3)")
        try:
            self.db.insert_bulk("x", (None, "b"), [(1, 2)])
            self.fail()
        except KinetaError:
            self.assertEqual(errstr(), "Cannot insert into a null column name")
        try:
            self.db.insert("x", {None: 1, "b": 2})
            self.fail()
        except KinetaError:
            self.assertEqual(errstr(), "Cannot insert into a null column name")

    def testInvalidIdentity(self):
        try:
            self.db.insert("x", {"a": {}})
            self.fail()
        except KinetaError:
            self.assertEqual(errstr(), "Unknown custom value definition")
        try:
            self.db.insert("a", {"a": {"identity": 1}})
            self.fail()
        except KinetaError:
            self.assertEqual(errstr(), "Identity value is 1, must be True")

    def testInvalidOrder(self):
        try:
            self.db.select(table="x", fields="a", order=[[]])
            self.fail()
        except KinetaError:
            self.assertEqual(errstr(), "Invalid order field []")
        try:
            self.db.select(table="x", fields="a", order=[["a", "x"]])
            self.fail()
        except KinetaError:
            self.assertEqual(errstr(), "Invalid sort direction x")
        try:
            self.db.select(table="x", fields="a", order=[["a"], ["a"]])
            self.fail()
        except KinetaError:
            self.assertEqual(errstr(), "Cannot order by field 'a' twice")

    def testInvalidExpr(self):
        try:
            self.db.select(table="x", fields="a", where={"a": []})
            self.fail()
        except KinetaError:
            self.assertEqual(errstr(), "Invalid empty where condition")
        try:
            self.db.select(table="x", fields="a", where={"a": ("invalid", "")})
            self.fail()
        except KinetaError:
            self.assertEqual(errstr(), "Unknown operator 'invalid'")
        try:
            self.db.select(table="x", fields="a", where={"a": ("==", 1, 2)})
            self.fail()
        except KinetaError:
            self.assertEqual(errstr(), "== operator takes 1 argument")
        try:
            self.db.select(table="x", fields="a", where={"a": ("between", 1)})
            self.fail()
        except KinetaError:
            self.assertEqual(errstr(), "between operator takes 2 arguments")

    def testInvalidUpdate(self):
        try:
            self.db.update("x", {None: 1})
            self.fail()
        except KinetaError:
            self.assertEqual(errstr(), "Cannot update a null column name")

    def testInvalidLimitOffset(self):
        try:
            self.db.update("x", {"a": 1}, limit=-1)
            self.fail()
        except KinetaError:
            self.assertEqual(errstr(), "limit must be >= 0")
        try:
            self.db.update("x", {"a": 1}, offset=-1)
            self.fail()
        except KinetaError:
            self.assertEqual(errstr(), "offset must be >= 0")

    def tearDown(self):
        self.db.close()


class ServerErrorTests(unittest.TestCase):

    def setUp(self):
        self._dbdir = "db"
        if os.path.isdir(self._dbdir):
            shutil.rmtree(self._dbdir)
        self.server = server.TestServer()
        self.server.app.databases['db'].set_bg_error_cb(self.bgerror)
        self.server.app.databases['db'].limits.server_obj_timeout = .1
        self.url = "http://localhost:20000/"

    def bgerror(self, exc_info):
        traceback.print_exception(*exc_info)

    def testInvalid(self):
        try:
            urllib2.urlopen(self.url)
        except:
            self.assertEqual(errstr(), "HTTP Error 405: Method Not Allowed")
        try:
            urllib2.urlopen(self.url + "test", "{}")
        except:
            self.assertEqual(errstr(), "HTTP Error 404: Not Found")
        try:
            urllib2.urlopen(self.url + "db/test", "{}")
        except:
            self.assertEqual(errstr(), "HTTP Error 400: Bad Request")
        try:
            urllib2.urlopen(self.url + "db/select/test", "{}")
        except:
            self.assertEqual(errstr(), "HTTP Error 400: Bad Request")
        try:
            urllib2.urlopen(self.url + "db/list_tables", "[]")
        except:
            self.assertEqual(errstr(), "HTTP Error 400: Bad Request")
        try:
            urllib2.urlopen(self.url + "db/list_tables", "{thisisbadjson[")
        except:
            self.assertEqual(errstr(), "HTTP Error 400: Bad Request")

    def testTxnTimeout(self):
        with RemoteDB(self.url + "db") as db:
            txn = db.begin()
            time.sleep(.3)
            try:
                txn.commit()
            except:
                self.assert_(errstr().startswith("HTTP Error 500: Internal "
                                                 "Server Error (Unknown "
                                                 "transaction "))

    def tearDown(self):
        self.server.quit()
        urllib2.urlopen(self.url + "db/list_tables", "{}").close()
        self.server.join()
